iT邦幫忙

2022 iThome 鐵人賽

DAY 3
0
Software Development

Kotlin on the way系列 第 3

Day3 Readability 為可讀性做設計

  • 分享至 

  • xImage
  •  

Who am I?
Who am I?
24601~~~
Les Misérables

老樣子,中文在下面

naming

In the topic of readability, naming is the most important thing, a good name should represent the intention of the program, just check the gif above, some of you might know 24601 because Hugh Jackman cast the movie, but is it a good naming? No, it requires you to understand the movie, the number itself is meaningless, just like list1, list2, myArr, dt, pk, sk ...etc, add a number, m___, weird abbreviations are common mistakes.

Does the dt stand for data or date or dayTime ?
Does the pk stand for public key ot publish key or partition key or Player Killer?

although it still difficult to find the precise word for each action, but there are still few rule we can follow

First, name should represent intention

/*
 * @param a means user age
 * */
data class UserData(
    val a:Int
)

the field a didn't specific its means, it should rename to

data class UserData(
    val age:Int
)

now the field can represent itself, and doesn't need comment to explain it, actually in most of time, if you need comment to explain naming, you are using a terrible name

Back to the data class naming, now we have UserData . If another developer adds another class called UserInfo, can you tell the difference?

I think you can't, probably the developer will forget in next month, if you trying to having a good name, first thing you care is language syntax, in most programing we state and behavior, the design of Kotlin data class, is helpful and mainly use as its modifier data to store state, In the way, you can just name it data class User()

don't use magic number

Socall magic number is a number inside your logic without what it is

amount * 5 /100

...

if(amount > 100)

no one can understand what those logic set for, but if with naming, it will be clearly,

amount * taxRate

...

if(amount > minimumOrderAmount)

on the other side, the naming also affect on its scope, for instance, you might consider set the national tax to a global constant variable, in Kotlin we will name it like

const val SALE_TAX = 0.05f

with all capitalized represent it can access globally

use single alphabet carefully

we only time we tolerant single alphabet like i, j, k, is inside loop parameter, it is a Fortran way, but I personally don't recommend it, while Kotlin have many convenient for loop syntax, i,j,k only confusing us, although it is kind of tradition

other alphabet should never use them as name, especially O and l

interface and implementation

There are two common way for this situation

interface AdapterFactory {
    
}
class AdapterFactoryImpl {
    
}
interface IAdapterFactory {
    
}
class AdapterFactory {
    
}

well, if your project is ongoing, just following the consistence, but I recommend you use the first one, because the class use AdapterFactory should care wherever they are using an interface or not, they just know they will receive an AdapterFactory

name state and behavior

class can hold state, so name of class and object, field, should use noun for its name, and function usually have behavior, it should named by Verb, or verb with noun, but the usecase is kind of speical, we wrap a behavior inside a class, so due to its intention, we will name it as behavior.

common structure

Check the following example

//implementataion a
if (person != null && person.isAdult){
    view.showPerson()
} else {
    view.showError()
}

//implementation b

person?.takeIf(it.isAdult)
    ?.let(View::showPerson)
    ?: view.showError()

do you think the second one is better? Well, I don't think so, I only saw showing off Kotlin syntax, and it cost more time to understand, it require reader having knowledge in nullish, receiver and function object, while the first one is simple and clear, let's try add another function in both condition

//implementataion a
if (person != null && person.isAdult){
    view.showPerson()
    view.showPerson()
} else {
    view.showError()
    view.showError()
}

//implementation b

person?.takeIf(it.isAdult)
    ?.let{
        view.showPerson()
        view.showPerson()
    }
    ?: run {
         view.showError()
         view.showError()
    }

as you can see, second one doesn't make our code shorter, only make it harder to read and refactor for when statement

understand those build-in operator

Although common structure is easy to understand, but Kotlin provide powerful collection operator to make your code cleaner

// replace this
users.onEach { 
    if(it <= 2) return 
    it
}
// to this
users.filter { it > 2 }

langauge syntax

named argument

using named argument is helpful, it could indicate what value it expect to received, especially when your function require same type of argument

Location(lat= 23.00352, lng = 121.24234)

中文

命名

在可讀性的主題中,命名是李最為重要的,一個好的命名可以表示程式的意圖,如同上面的圖片,有人知道24601是因為休傑克曼主演了電影,但24601 是一個好的命名嗎? 不,這個代稱要求我們需要了解電影,而數字本身是無意義的,相似的情境也常在程式中看到 list1, list2, myArr, dt, pk, sk 等,加數字、無用前綴、奇怪的縮寫是經常發現的錯誤

dt 是代表 datadatedayTime ?
pk 是代表 public keypublish keypartition keyPlayer Killer?

儘管找到準確的詞彙很難,但我們可以遵循幾點

第一,命名應稱份展現意圖

/*
 * @param a means user age
 * */
data class UserData(
    val a:Int
)

現在的參數,並無法表示他的用途,需要靠閱讀註解

data class UserData(
    val age:Int
)

現在參數可以代表自己了,在多數時間,如果你需要註解來表示命名的用途,那這個命名就是不夠好

回到資料類別的命名,現在有了UserData ,如果有人命名了 UserInfo 你分得出區別嗎?

我認為不行,甚至那個開發者在一個月後也不會記得,要想有好的命名,要先在意的是情境,程式裡有 狀態 和 行為 兩種,而Kotlin 裡面的資料類別,主要就是用來保存狀態的,而我們能將其命名為data class User()

不要有 magic number

所謂的 magic number 是在你的程式裡,突然出現的數字

amount * 5 /100

...

if(amount > 100)

沒人可以立刻理解這個邏輯想要幹嘛,但如果有命名,就不必猜測

amount * taxRate

...

if(amount > minimumOrderAmount)

另一方面,命名也會取決於他能影響的範圍,在 Kotlin 裡,如果要做全局參數,我們會將其命成全大寫,是一個默認規則

const val SALE_TAX = 0.05f

小心字母變數

我們只會容忍在迴圈裡面的 i, j , k 變數,這是從 Fortran 時期遺留下來的潛在規範,然而我個人還是推薦即使在迴圈裡,變數命名仍應有其意義

在其他地方,不應使用單個字母當作變數名稱,尤其是O and l

介面與實作

介面和實作的命名有兩種常見的方式

interface AdapterFactory {
    
}
class AdapterFactoryImpl {
    
}
interface IAdapterFactory {
    
}
class AdapterFactory {
    
}

如果你的專案已經在進行,那你應該維持一致性,使用相同的方式,如果不是,那使用第一種會更好,因為依賴 AdapterFactory 的類別不應在意他拿到的是介面還是實作,他們只需知道他們會拿到 AdapterFactory

為狀態和行為命名

類別可以保存狀態,為類別、物件、參數 可以使用名詞命名,而函式通常是一個行為,應使用動詞,或動詞加名詞表示,而 usecase 較特別,我們將行為包進類別裡,使我們能透過物件導向的方式管理、覆用,這種情況我們仍會用動詞加名詞命名

常見結構

看下面的範例

//implementataion a
if (person != null && person.isAdult){
    view.showPerson()
} else {
    view.showError()
}

//implementation b

person?.takeIf(it.isAdult)
    ?.let(View::showPerson)
    ?: view.showError()

你認為第二個會更好嗎?不,我只看到有人在秀語法技巧,但那段程式更難理解,他要求閱讀者理解 null處理、接收者、函式物件,而第一個結構更簡單且乾淨,現在我們在裡面加入第二件事

//implementataion a
if (person != null && person.isAdult){
    view.showPerson()
    view.showPerson()
} else {
    view.showError()
    view.showError()
}

//implementation b

person?.takeIf(it.isAdult)
    ?.let{
        view.showPerson()
        view.showPerson()
    }
    ?: run {
         view.showError()
         view.showError()
    }

如你所見,第二個方式也無法讓程式碼變短,只是讓他更難閱讀和為 when 重構

理解內建的操作符

儘管常見結構很好理解,但 Kotlin 也提供了許多操作符,可以使程式更清晰,像是 collection 和 flow,適當的選用工具,才能讓程式碼好讀

// replace this
users.onEach { 
    if(it <= 2) return 
    it
}
// to this
users.filter { it > 2 }

語法

具名參數

使用具名參數很方便,他可以代表期望收到的變數是什麼,尤其在接收的參數是相同類型的時候

Location(lat= 23.00352, lng = 121.24234)

上一篇
Day2 Design of project 專案設計
下一篇
Day 4 別當連 if 都寫爛的工程師 Make your if statement better
系列文
Kotlin on the way31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言